/**
 * @author YJSYS
 *
 */

package com.qd.extcamclientv2;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;

import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

public class ClientAsynkTask extends AsyncTask<Void, String, String> {
	public static final String TAG = "ExtCamClientV2";

	private void _log(String log) {
		Log.i(TAG, "ClientAsynkTask: " + log);
	}

	private static final int SOCKET_TIMEOUT = 30000;
	private static int SERVER_PORT = 8988;

	public static final int CMD_START_RECORD = 0;
	public static final int CMD_STOP_RECORD = 1;
	public static final int CMD_GET_DATA = 2;
	public static final int CMD_GET_FILE_LIST = 3;
	public static final int CMD_GET_FILE = 4;
	public static final int CMD_DELETE_FILE = 5;
	public static final int CMD_LOGOUT = 6;
	public static final int CMD_GET_STATUS = 7;

	private static final int PRT_POS_DATA = 7;
	private static final int PRT_POS_COMMAND = 2;
	private static final int PRT_POS_RESULT = 1;
	private static final int PRT_POS_LEN = 3;

	private Context mContext = null;
	private Handler mHandler = null;
	private String mHostAddress = "";
	private Socket mSocket = null;
	private boolean mIsRunning = false;
	private ProgressDialog mProgressDlg = null;
	private byte[] mQueue = null;
	private int mQueueIndex = 0;
	private FileOutputStream mDownloadFile = null;
	private long mDownloadFileTotalLength = 0;
	private long mDownloadFileProgress = 0;

	/**
	 * 서버 접속을 위한 소켓을 생성하고 서버와 통신 작업을 한다.
	 * 
	 * @param context
	 * @param statusText
	 */
	public ClientAsynkTask(Context context, Handler handler, String hostAddr) {
		mContext = context;
		mHandler = handler;
		mHostAddress = hostAddr;
		mIsRunning = true;
		mQueue = new byte[1024000];
		mQueueIndex = 0;
	}

	/* (non-Javadoc)
	 * @see android.os.AsyncTask#onPreExecute()
	 */
	@Override
	protected void onPreExecute() {
		_log("onPreExecute()");

		mProgressDlg = ProgressDialog.show(mContext, "Network Connecting",
				"Please Wait...", false);
		mProgressDlg.setCancelable(false); // 소켓 생성시에는 취소할 수 없다.

		super.onPreExecute();
	}

	
	/* (non-Javadoc)
	 * @see android.os.AsyncTask#onProgressUpdate(Progress[])
	 */
	@Override
	protected void onProgressUpdate(String... params) {
		_log("onProgressUpdate(" + params[0] + ")");
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see android.os.AsyncTask#doInBackground(Params[])
	 * 소켓을 생성한후 서버에 접속하여 서버와 통신한다.
	 */
	@Override
	protected String doInBackground(Void... params) {
		mSocket = new Socket();
		try {
			_log("Start client socket +++");

			mSocket.bind(null);
			mSocket.connect(
					(new InetSocketAddress(InetAddress.getByName(mHostAddress),
							SERVER_PORT)), SOCKET_TIMEOUT);
			_log("connected... ");
			mProgressDlg.dismiss();
			mProgressDlg = null;

			mHandler.sendEmptyMessageDelayed(
					RecorderActivity.HANDLER_SOCKET_INIT, 100);

			InputStream is = mSocket.getInputStream();
			byte[] buffer = new byte[256];
			int receivedLen = 0;
			while (mIsRunning) {
				receivedLen = is.read(buffer);
				if (receivedLen > 0) {
					// _log("receivedLen = " + receivedLen);
					addQueue(buffer, receivedLen);
					doResponse();
				}
			}
		} catch (IllegalArgumentException e) {
			_log("IllegalArgumentException " + e.getMessage());
		} catch (IOException e) {
			_log("IOException " + e.getMessage());
		} catch (Exception e) {
			_log("Exception " + e.getMessage());
		} finally {
			closeSocket();
		}

		_log("Stop client socket ---");

		return null;
	}

	/* (non-Javadoc)
	 * @see android.os.AsyncTask#onCancelled()
	 */
	@Override
	protected void onCancelled() {
		_log("onCancelled()");

		closeSocket();
		if (null != mProgressDlg) {
			mProgressDlg.dismiss();
		}

		super.onCancelled();
	}

	/* (non-Javadoc)
	 * @see android.os.AsyncTask#onPostExecute(java.lang.Object)
	 */
	@Override
	protected void onPostExecute(String result) {
		_log("onPostExecute(" + result + ")");

		closeSocket();
		if (null != mProgressDlg) {
			mProgressDlg.dismiss();
		}

		super.onPostExecute(result);
	}

	/**
	 * @param command 명령 구분
	 * @param arg1 데이터
	 * 서버에 명령을 전달한다.
	 */
	public void sendCommand(int command, String arg1) {
		_log("sendCommand(" + command + ", " + arg1 + ")");

		if (CMD_LOGOUT == command) {
			closeSocket();
		} else {
			try {
				if (mSocket != null && mSocket.isConnected()) {
					if (CMD_GET_DATA != command) {
						mProgressDlg = new ProgressDialog(mContext);
						mProgressDlg.setTitle("Network Connecting");
						mProgressDlg.setMessage("Please Wait...");
						mProgressDlg.setCancelable(true);
						mProgressDlg
								.setOnCancelListener(new OnCancelListener() {
									@Override
									public void onCancel(DialogInterface dialog) {
										cancel(true);
									}
								});
						if (CMD_GET_FILE == command) {
							mProgressDlg.setIndeterminate(false);
							mProgressDlg.setMax(100);
							mProgressDlg
									.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
						} else {
							mProgressDlg.setIndeterminate(true);
						}
						mProgressDlg.show();
					}

					OutputStream os = mSocket.getOutputStream();
					if (arg1 != null && arg1.length() > 0) {
						os.write(makePacket(command, arg1.getBytes()).toBytes());
					} else {
						os.write(makePacket(command, null).toBytes());
					}
					os.flush();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * 소켓을 닫고 로그아웃한다.
	 */
	private void closeSocket() {
		if (mSocket != null && mSocket.isConnected()) {
			mIsRunning = false;
			try {
				mSocket.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			mSocket = null;
		}

		mHandler.sendEmptyMessage(CMD_LOGOUT);
	}

	/**
	 * 서버로 부터 받은 데이터를 처리한다.
	 */
	private void doResponse() {
		byte[] dataBuf = null;
		int nDataLen = 0;

		for (;;) {
			if (mQueueIndex <= PRT_POS_DATA) {
				break;
			}

			int nPackLen = (mQueue[PRT_POS_LEN] << 24)
					+ ((mQueue[PRT_POS_LEN + 1] & 0xff) << 16)
					+ ((mQueue[PRT_POS_LEN + 2] & 0xff) << 8)
					+ (mQueue[PRT_POS_LEN + 3] & 0xFF);
			// publishProgress("!!! nPackLen : " + nPackLen);
			nPackLen += 8;
			if (nPackLen > mQueueIndex) {
				break;
			}

			int cmd = mQueue[PRT_POS_COMMAND];
			// publishProgress("!!! nPackCmd : " + cmd);
			if (mQueue[PRT_POS_DATA] != (byte) 0xf2) {
				nDataLen = (nPackLen - 8);
				dataBuf = new byte[nDataLen];
				System.arraycopy(mQueue, PRT_POS_DATA, dataBuf, 0, nDataLen);
			}

			removeQueue(nPackLen);

			Message msg = new Message();
			msg.what = cmd;
			msg.arg1 = mQueue[PRT_POS_RESULT];

			/* error check */
			switch (cmd) {
			case CMD_START_RECORD:
				if (0 == msg.arg1) {
					publishProgress("START_RECORD");
				} else {
					msg.obj = new String(dataBuf);
					publishProgress("START_RECORD : " + msg.obj.toString());
				}
				break;
			case CMD_STOP_RECORD:
				if (0 == msg.arg1) {
					publishProgress("STOP_RECORD");
				}
				break;
			case CMD_GET_FILE_LIST:
				if (0 == msg.arg1) {
					msg.obj = new String(dataBuf);
					publishProgress("CMD_GET_FILE_LIST : " + msg.obj.toString());
				}
				break;
			case CMD_DELETE_FILE:
				if (0 == msg.arg1) {
					if (null == dataBuf) {
						publishProgress("CMD_DELETE_FILE : ALL");
					} else {
						msg.obj = new String(dataBuf);
						publishProgress("CMD_DELETE_FILE : "
								+ msg.obj.toString());
					}
				}
				break;
			case CMD_GET_FILE:
				if (0 == msg.arg1) {
					msg.obj = new String(dataBuf);
					publishProgress("GET_FILE : " + ((String) msg.obj).length());
					msg.arg2 = fileDownload(dataBuf);
					if ((1 == msg.arg2) || (2 == msg.arg2)) {
						// Start, ...
						mHandler.sendMessage(msg);
						continue;
					} else {
						// End or Error
					}
				}
				break;
			case CMD_GET_DATA:
				if (0 == msg.arg1) {
					msg.obj = new String(dataBuf);
					publishProgress("GET_DATA : " + ((String) msg.obj).length());
				}
				break;
			case CMD_LOGOUT:
				if (0 == msg.arg1) {
					publishProgress("LOGOUT");
				}
				break;
			case CMD_GET_STATUS:
				if (0 == msg.arg1) {
					msg.arg2 = dataBuf[0];
					publishProgress("GET_STATUS : " + msg.arg2);
				}
				break;
			}
			if (null != mProgressDlg && mProgressDlg.isShowing()) {
				mProgressDlg.dismiss();
				mProgressDlg = null;
			}
			mHandler.sendMessage(msg);
		}
	}

	/**
	 * 파일을 다운로드 한다.
	 * 
	 * @param data
	 *            파일 데이터
	 */
	private int fileDownload(byte[] data) {
		if (data == null) {
			publishProgress("ERROR : File Download");
			return 0;
		}

		switch (data[0]) {
		case 1: // header, file create
			publishProgress("File Download Start");
			byte[] fileInfoByte = new byte[data.length - 1];
			System.arraycopy(data, 1, fileInfoByte, 0, data.length - 1);

			String fileName = new String(fileInfoByte);
			String[] tokens = fileName.split("##");
			if (tokens != null) {
				for (int i = 0; i < tokens.length; i++) {
					switch (i) {
					case 0:
						fileName = tokens[i];
						break;
					case 1:
						mDownloadFileTotalLength = Long.parseLong(tokens[i]);
						mDownloadFileProgress = 0;
						break;
					}
				}
			}

			File file = new File(MainActivity.EXTCAM_DIR, fileName);
			try {
				mDownloadFile = new FileOutputStream(file);
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			}
			break;
		case 2: // file data
			if (mDownloadFile != null) {
				try {
					int byteCount = data.length - 1;
					mDownloadFile.write(data, 1, byteCount);

					mDownloadFileProgress += byteCount;
					int percent = (int) ((mDownloadFileProgress * 100)
							/ mDownloadFileTotalLength + 0.5);
					// publishProgress("File Downloading ... " + percent +
					// "[%]");
					mProgressDlg.setProgress(percent);
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			break;
		case 3: // tail
			if (mDownloadFile != null) {
				publishProgress("File Download End");
				try {
					mDownloadFile.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
				mDownloadFile = null;
			}
			break;
		}

		return data[0];
	}

	/**
	 * 서버로 부터 받은 데이터를 MessageQueue에 추가한다.
	 * 
	 * @param buffer
	 *            데이터
	 * @param nRcvdLen
	 *            데이터 길이
	 */
	private void addQueue(byte[] buffer, int nRcvdLen) {
		System.arraycopy(buffer, 0, mQueue, mQueueIndex, nRcvdLen);
		mQueueIndex += nRcvdLen;
	}

	/**
	 * MessageQueue에서 서버 데이터를 삭제한다.
	 * 
	 * @param nPackLen
	 *            서버데이터 길이
	 */
	private void removeQueue(int nPackLen) {
		System.arraycopy(mQueue, nPackLen, mQueue, 0, mQueueIndex - nPackLen);
		mQueueIndex -= nPackLen;
	}

	/**
	 * @param command
	 * @param data
	 * @return
	 */
	private Packet makePacket(int command, byte[] data) {
		int dataLen = 0;
		if (data != null)
			dataLen = data.length;

		Packet packet = new Packet();
		packet.rslt = 0;
		packet.command = (byte) command;
		packet.len = dataLen;
		packet.data = data;

		return packet;
	}

	/**
	 * 서버와 통신을 위한 데이터 클래스
	 */
	private class Packet {
		public byte header = (byte) 0xf1;
		public byte rslt;
		public byte command;
		public int len;
		public byte[] data;
		public byte tail = (byte) 0xf2;

		/**
		 * @return 패킷 정보를 바이트 형태로 반환한다.
		 */
		public byte[] toBytes() {
			int buffer_len = 8 + len;
			byte[] buffer = new byte[8 + len];

			buffer[0] = header;
			buffer[1] = rslt;
			buffer[2] = command;
			buffer[3] = (byte) ((len >>> 24) & 0xFF);
			buffer[4] = (byte) ((len >>> 16) & 0xFF);
			buffer[5] = (byte) ((len >>> 8) & 0xFF);
			buffer[6] = (byte) ((len >>> 0) & 0xFF);
			if (data != null)
				System.arraycopy(data, 0, buffer, 7, len);
			buffer[buffer_len - 1] = tail;

			return buffer;
		}
	}
}
